/*
 * yauosk - Yet Another Useless Operating System Kernel
 *
 * Copyright (c) 2009-2010 Matteo Cicuttin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <systm.h>
#include <sys/types.h>
#include <arch/machine.h>

/* See MPS 1.4 */
#define MP_SIG	0x5f504d5f	/* _MP_ */
#define PCMP_SIG 0x504d4350 /* PCMP */

#define PROCESSOR	0
#define BUS			1
#define	IOAPIC		2
#define	IOINTR		3
#define LOCALINTR	4

#define MPCONFIG_ISA				1
#define MPCONFIG_EISA_NO_TIMER_DMA	2
#define MPCONFIG_EISA				3
#define MPCONFIG_MCA				4
#define MPCONFIG_ISA_PCI			5
#define MPCONFIG_EISA_PCI			6
#define MPCONFIG_MCA_PCI			7


uint lapicaddr;
uint smp_num_cpus;
uint sys_maxbus;
uint sys_apics;
uint sys_ints;
uint bsp_id;
uint sys_is_smp;

/* Floating pointer structure (pages 4-3 and 4-4) */
struct mpsfp
{
	uchar	signature[4];
	uint	pap;
	uchar	length;
	uchar	spec_rev;
	uchar	checksum;
	uchar	mp_fib1;
	uchar	mp_fib2;
	uchar	resv[3];
};

/* Configuration table header (page 4-5) */
struct mpscth
{
	uchar	signature[4];
	ushort	length;
	uchar	spec_rev;
	uchar	checksum;
	uchar	oem_id[8];
	uchar	product_id[12];
	uint	oem_tabp;
	ushort	oem_tabsz;
	ushort	entries;
	uint	lapic_addr;
	ushort	ext_len;
	uchar	ext_checksum;
	uchar	resv;
};

/* Base table processor entry */
struct mpspe
{
	uchar	type;
	uchar	lapic_id;
	uchar	lapic_ver;
	uchar	cpu_flags;
	uint	cpu_sig;
	uint	features;
	uint	resv0;
	uint	resv1;
};

struct mpsbe
{
	uchar type;
	uchar bus_id;
	uchar type_string[6];
};

struct mpsapic
{
	uchar	type;
	uchar	apic_id;
	uchar	apic_ver;
	uchar	apic_flags;
	uint	mmio_apic_addr;
};

struct mpsint
{
	uchar	type;
	uchar	itype;
	ushort	iflags;
	uchar	source_id;
	uchar	source_irq;
	uchar	dest_id;
	uchar	dest_irq;
};

struct mpspe	*mpspes[MAXPE];
struct mpsbe	*mpsbes[MAXBE];
struct mpsapic	*mpsapics[MAXAPIC];
struct mpsint	*mpsints[MAXINTR];

struct cpu cpu[MAXPE];

/*
 * Search for the MP floating pointer in the following areas:
 *  a. First kB of EBDA
 *  b. Last kB of base memory
 *  c. In the BIOS ROM space between 0xF0000 and 0xFFFFF
 */
 
static struct mpsfp*
smp_mpsfp_search_sig(uint startaddr, int len)
{
	uint *addr = (uint *) startaddr;
	uint ofs;
	for(ofs = 0; ofs < len; ofs++)
		if (addr[ofs] == MP_SIG)
			return (struct mpsfp*) (startaddr + (ofs * sizeof(uint)));
	return NULL;
}

static struct mpsfp*
smp_mpsfp_find(void)
{
	uint segment;
	struct mpsfp *fp;
	
	if ( (segment = (uint) *(ushort*) 0x40e ) != 0)
	{
		if ( (fp = smp_mpsfp_search_sig(segment<<4, 256)) != NULL )
			return fp;
	}
	else 
	{
		/* Assumes 640k of base memory */
		fp = smp_mpsfp_search_sig(0x9FC00, 256);
		if (fp)
			return fp;
	}
	
	/* Search in the 0xF0000 - 0xFFFFF range */
	fp = smp_mpsfp_search_sig(0xF0000, 0x10000/4);
	if (fp)
		return fp;
		
	return NULL;
}

void
smp_boot_aps(void)
{
	int i;
	extern u_int8_t _binary_sys_boot_x86_trampoline_start[];
	extern u_int8_t _binary_sys_boot_x86_trampoline_size[];	
	extern void startup_ap(void);
	memmove((u_int8_t *)MP_ENTRY,
		  _binary_sys_boot_x86_trampoline_start,
		  _binary_sys_boot_x86_trampoline_size
		  );
	
	for (i = 0; i < smp_num_cpus; i++)
	{
		*(u_int32_t *) (MP_ENTRY-4) = cpu[i].stack + BOOTSTKSIZE;
		*(u_int32_t *) (MP_ENTRY-8) = startup_ap;
		
		if (i == thiscpu())
			continue;
		
		lapic_startap(cpu[i].id, 0x7000);
		while (!cpu[i].online)
			;
		
	}
	
	printf("%d processors online\n", smp_num_cpus);
}

void
smp_check(void)
{
	struct mpsfp *fp;
	struct mpscth *cth;
	struct mpsint *intr;
	uchar *ttype, *end;
	uchar b;
	
	smp_num_cpus = 0;
	sys_maxbus = 0;
	sys_apics = 0;
	sys_ints = 0;
	lapicaddr = 0;
	sys_is_smp = 1;
	
	fp = smp_mpsfp_find();
	if (!fp)
	{
		sys_is_smp = 0;
		printf("This machine is not SMP\n");
		return;
	}

	cth = (struct mpscth *) fp->pap;
	if (!cth)
		return;
	
	if ( (*(uint*)cth) != PCMP_SIG )
	{
		printf("Invalid signature %x at %p\n", (*(uint*)cth), cth);
		return;
	}
	
	lapicaddr = cth->lapic_addr;
	
	/* Section 10.4.1 of intel software developer's manual tells that
	 * local apic is at 0xFEE00000, so lapic_addr should have this value
	 */
	 
	ttype = ((uchar *) cth) + sizeof(struct mpscth);
	end = ((uchar *) cth) + cth->length;
	
	while (ttype < end)
	{
		switch(*ttype)
		{
			case PROCESSOR:
				//printf("%p: Found MP Table Processor entry\n", ttype);
				mpspes[smp_num_cpus] = (struct mpspe*) ttype;
				cpu[smp_num_cpus].id = mpspes[smp_num_cpus]->lapic_id;
				cpu[smp_num_cpus].online = 0;
				/* Is this the bootstrap processor? */
				if (mpspes[smp_num_cpus]->cpu_flags & 0x02)
					bsp_id = smp_num_cpus;
				
				smp_num_cpus++;
				ttype += sizeof(struct mpspe);
				break;
				
			case BUS:
				//printf("%p: Found MP Table Bus entry\n", ttype);
				mpsbes[sys_maxbus] = (struct mpsbe*) ttype;
				sys_maxbus++;
				ttype += sizeof(struct mpsbe);
				break;
				
			case IOAPIC:
				//printf("%p: Found MP Table IOAPIC entry\n", ttype);
				/* ioapic.c should get ioapic address from here */
				mpsapics[sys_apics] = (struct mpsapic*) ttype;
				sys_apics++;
				ttype += sizeof(struct mpsapic);
				break;
				
			case IOINTR:
			case LOCALINTR:
				//printf("%p: Found MP Table Interrupt entry\n", ttype);
				mpsints[sys_ints] = (struct mpsint*) ttype;
				sys_ints++;
				
				/*
				printf("etype: %d, itype: %d, sid: %d, sirq: %d, did: %d, %d\n",
						intr->type, intr->itype, intr->source_id,
						intr->source_irq, intr->dest_id, intr->dest_irq);
				*/
				ttype += sizeof(struct mpsint);
				break;
				
			default:
				printf("Unknown entry found\n");
				while (ttype < end)
			 		ttype++;
		}
	}
	
	printf("%d CPU(s) found\n", smp_num_cpus);
	printf("%d BUS(es) found\n", sys_maxbus);
	printf("%d APIC(s) found\n", sys_apics);
	printf("%d Interrupts(s) found\n", sys_ints);
	
	/* Kick in APIC */
	if (fp->mp_fib2)
	{
		printf("fib2\n");
		outb(0x22, 0x70);
		b = inb(0x23);
		b |= 0x01;
		outb(0x23, b);
	}
}
